E2619 - Student Quiz Frontend (solved diff)#181
Conversation
📝 WalkthroughWalkthroughThis PR introduces quiz questionnaire support throughout the application and adds a new "AssignedReviews" student page for managing peer and quiz reviews. Changes span routing, form components, questionnaire editing and authoring, teammate review submission, and assignment configuration, enabling quiz creation, quiz taking, and reviewer-to-team mapping persistence. Changes
Sequence DiagramssequenceDiagram
actor User
participant Client as Browser/React
participant Server as Backend API
User->>Client: Navigate to AssignedReviews
Client->>Server: GET /assignments/{id}
Server-->>Client: Assignment details
Client->>Server: GET /student_tasks/list
Server-->>Client: Student task quiz requirements
Client->>Server: GET /response_maps (filtered by reviewer_user_id)
Server-->>Client: Response maps and responses
Client->>Client: Build review rows from questionnaires<br/>and response states
Client->>User: Render reviews table with status
User->>Client: Click "Take Quiz" button
Client->>Server: POST /quiz_response_maps
Server-->>Client: New quiz response map
Client->>Client: Navigate to /response/new with<br/>quiz map/questionnaire params
User->>Client: Submit quiz responses
Client->>Server: PATCH /responses/{id}/submit
Server-->>Client: Confirmation + total_score
Client->>User: Show quiz success UI
sequenceDiagram
actor Instructor
participant Client as Browser/React
participant Server as Backend API
Instructor->>Client: Access Questionnaire Editor
Client->>Client: Initialize form with questionnaire_type=Quiz
Instructor->>Client: Fill quiz questions + correct answers
Client->>Client: Render correct_answer controls<br/>(checkboxes, scales, dropdowns, etc.)
Instructor->>Client: Save questionnaire
Client->>Server: POST /questionnaires (with items including correct_answer)
Server-->>Client: Created questionnaire {id}
alt If team_id present (creating quiz for team)
Client->>Server: PATCH /teams/{teamId}/quiz_questionnaire
Server-->>Client: Team quiz_questionnaire_id updated
end
Client->>Client: Navigate back (return_to or /questionnaires)
par Student takes quiz
Instructor->>Client: Create response map for student quiz
Client->>Client: Render TeammateReview in quiz mode
Client->>Client: Display items with correct answer indicators
Instructor->>Client: Select answers for each item
Client->>Server: PATCH /responses/{id}/submit
Server-->>Client: Scoring result (total_score)
Client->>Client: Display score + success message
end
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Review rate limit: 8/10 reviews remaining, refill in 11 minutes and 6 seconds. Comment |
There was a problem hiding this comment.
Actionable comments posted: 5
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
src/pages/Assignments/AssignmentEditor.tsx (1)
56-102:⚠️ Potential issue | 🟠 Major | ⚡ Quick winRestore the
is_role_baseddefault ininitialValues.The form still binds
name="is_role_based"later, andtransformAssignmentRequeststill serializes this field. Removing it from the defaults leaves new assignments with an undefined checkbox state instead of a predictablefalse.🔧 Suggested fix
review_rubric_varies_by_role: false, + is_role_based: false, has_max_review_limit: false,🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/pages/Assignments/AssignmentEditor.tsx` around lines 56 - 102, The initialValues object is missing the is_role_based default which causes the checkbox bound via name="is_role_based" to be undefined; update the initialValues (type IAssignmentFormValues) to include is_role_based: false so new assignments have a predictable false state and remain consistent with transformAssignmentRequest serialization.
🧹 Nitpick comments (1)
src/pages/StudentTasks/StudentTasks.tsx (1)
304-435: ⚡ Quick winRemove unused assigned-reviews fetching logic from this page.
These states/effects/callbacks are not consumed by the rendered Sign Up UI, but still trigger localStorage parsing and
/response_mapsrequests.Also applies to: 437-458
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/pages/StudentTasks/StudentTasks.tsx` around lines 304 - 435, The assigned-reviews logic is unused in the Sign Up UI; remove the unused state and its side-effect to stop localStorage parsing and /response_maps calls: delete the const [assignedReviews, setAssignedReviews] = useState<AssignedReviewRow[]>([]) declaration plus the entire useEffect that builds rows (including buildRows, isTeammateQuestionnaire, teammateQuestionnaire/normalReviewQuestionnaire logic and the localStorage + axiosClient.get('/response_maps') flow) and any direct references to setAssignedReviews or assignedReviews in this file; if other code relies on buildRows, isolate or move it, and ensure you also remove any now-unused imports (e.g. axiosClient) to keep the file clean.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@src/pages/Assignments/AssignReviewer.tsx`:
- Around line 491-495: The onCreateQuiz function always routes to the create
flow (/questionnaires/new) even when editing an existing team quiz; update the
logic so when a questionnaireId for an existing quiz is present it navigates to
the edit route instead (e.g.
navigate(`/questionnaires/${questionnaireId}/edit?team_id=${teamId}&assignment_id=${assignmentId}&return_to=${returnTo}`)),
and keep the current /questionnaires/new path only for true create actions;
modify the relevant handlers (onCreateQuiz and the analogous block referenced
around lines 601-608) to check for an existing questionnaireId or “isEdit” flag
and choose the correct route accordingly.
In `@src/pages/Questionnaires/QuestionnaireEditor.tsx`:
- Around line 77-88: The create-flow currently always navigates away even if the
team-linking PATCH (axiosClient.patch in the mode === "create" block) fails;
change the flow so navigation only happens after a successful link: move the
navigate(returnTo ? decodeURIComponent(returnTo) : "/questionnaires") and the
subsequent return into the try block after the axiosClient.patch completes, and
in the catch (linkErr) for the patch do not navigate—log or surface the error
and let the function continue without redirecting so the user stays on the page
to fix the issue.
In `@src/pages/Questionnaires/QuestionnaireItemsFieldArray.tsx`:
- Around line 516-536: The Add action allows pushing items with an empty
question_type when numQuestions is provided but questionType is unset; update
the onClick handlers (the one with push, questionType, numQuestions,
setNumQuestions, setQuestionType and the similar block at the later location) to
guard early: check that questionType is a non-empty string (and numQuestions is
a positive number) before calling push in the loop, and if invalid simply return
(or disable the button upstream); ensure you do the same guard in both
occurrences so no item is created with question_type === "".
In `@src/pages/Questionnaires/QuestionnaireUtils.tsx`:
- Line 121: Remove the two console.log calls that print full questionnaire
payloads (the calls printing "Original Form Values:" and the other at line 138)
so sensitive quiz/answer data is not emitted in normal runtime; instead either
delete them entirely or replace with a non-production-guarded debug logger
(e.g., wrap in if (process.env.NODE_ENV === 'development') or use a secure
logger.debug after redacting answers) and update the code paths in
QuestionnaireUtils.tsx where these console.log calls occur (search for the exact
string "Original Form Values:" to locate one of them).
In `@src/pages/StudentTasks/AssignedReviews.tsx`:
- Around line 272-278: buildRows is called without currentUserTeamId so
backendRows' AssignedReviewRow.isTeammateReview is incorrectly false and then
overwrites local rows during setAssignedReviews merge by mapId; fix by ensuring
buildRows receives currentUserTeamId (e.g., change call to buildRows(maps,
currentUserTeamId) and propagate that param into buildRows) or, when merging in
setAssignedReviews, preserve the existing prev row's isTeammateReview for the
same mapId (use prev's row fields where present, especially isTeammateReview) so
teammate classification isn't lost.
---
Outside diff comments:
In `@src/pages/Assignments/AssignmentEditor.tsx`:
- Around line 56-102: The initialValues object is missing the is_role_based
default which causes the checkbox bound via name="is_role_based" to be
undefined; update the initialValues (type IAssignmentFormValues) to include
is_role_based: false so new assignments have a predictable false state and
remain consistent with transformAssignmentRequest serialization.
---
Nitpick comments:
In `@src/pages/StudentTasks/StudentTasks.tsx`:
- Around line 304-435: The assigned-reviews logic is unused in the Sign Up UI;
remove the unused state and its side-effect to stop localStorage parsing and
/response_maps calls: delete the const [assignedReviews, setAssignedReviews] =
useState<AssignedReviewRow[]>([]) declaration plus the entire useEffect that
builds rows (including buildRows, isTeammateQuestionnaire,
teammateQuestionnaire/normalReviewQuestionnaire logic and the localStorage +
axiosClient.get('/response_maps') flow) and any direct references to
setAssignedReviews or assignedReviews in this file; if other code relies on
buildRows, isolate or move it, and ensure you also remove any now-unused imports
(e.g. axiosClient) to keep the file clean.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: c6a590f7-fcfe-46a3-9718-fd2fdc16100b
📒 Files selected for processing (16)
src/App.tsxsrc/components/Form/FormSelect.tsxsrc/hooks/useSignupSheet.tssrc/pages/Assignments/AssignReviewer.tsxsrc/pages/Assignments/AssignmentEditor.tsxsrc/pages/Assignments/AssignmentUtil.tssrc/pages/Assignments/tabs/GeneralTab.tsxsrc/pages/Questionnaires/Questionnaire.tsxsrc/pages/Questionnaires/QuestionnaireColumns.tsxsrc/pages/Questionnaires/QuestionnaireEditor.tsxsrc/pages/Questionnaires/QuestionnaireForm.tsxsrc/pages/Questionnaires/QuestionnaireItemsFieldArray.tsxsrc/pages/Questionnaires/QuestionnaireUtils.tsxsrc/pages/Student Teams/TeammateReview.tsxsrc/pages/StudentTasks/AssignedReviews.tsxsrc/pages/StudentTasks/StudentTasks.tsx
| function onCreateQuiz(teamId: Id) { | ||
| if (!hasValidId) return; | ||
| const returnTo = encodeURIComponent(`/assignments/edit/${assignmentId}/assignreviewer`); | ||
| navigate(`/questionnaires/new?type=Quiz&team_id=${teamId}&assignment_id=${assignmentId}&return_to=${returnTo}`); | ||
| } |
There was a problem hiding this comment.
edit quiz currently routes to create flow.
Line 607 advertises edit behavior, but Line 494 always sends users to /questionnaires/new. Existing team quizzes should route to edit mode.
Suggested fix
- function onCreateQuiz(teamId: Id) {
+ function onCreateQuiz(teamId: Id, quizQuestionnaireId?: Id | null) {
if (!hasValidId) return;
const returnTo = encodeURIComponent(`/assignments/edit/${assignmentId}/assignreviewer`);
- navigate(`/questionnaires/new?type=Quiz&team_id=${teamId}&assignment_id=${assignmentId}&return_to=${returnTo}`);
+ if (quizQuestionnaireId) {
+ navigate(`/questionnaires/${quizQuestionnaireId}/edit?type=Quiz&team_id=${teamId}&assignment_id=${assignmentId}&return_to=${returnTo}`);
+ return;
+ }
+ navigate(`/questionnaires/new?type=Quiz&team_id=${teamId}&assignment_id=${assignmentId}&return_to=${returnTo}`);
}- onClick={() => hasValidId && onCreateQuiz(team.id)}
+ onClick={() => hasValidId && onCreateQuiz(team.id, team.quiz_questionnaire_id)}Also applies to: 601-608
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/pages/Assignments/AssignReviewer.tsx` around lines 491 - 495, The
onCreateQuiz function always routes to the create flow (/questionnaires/new)
even when editing an existing team quiz; update the logic so when a
questionnaireId for an existing quiz is present it navigates to the edit route
instead (e.g.
navigate(`/questionnaires/${questionnaireId}/edit?team_id=${teamId}&assignment_id=${assignmentId}&return_to=${returnTo}`)),
and keep the current /questionnaires/new path only for true create actions;
modify the relevant handlers (onCreateQuiz and the analogous block referenced
around lines 601-608) to check for an existing questionnaireId or “isEdit” flag
and choose the correct route accordingly.
| if (mode === "create" && teamId && response.data?.id) { | ||
| try { | ||
| await axiosClient.patch( | ||
| `/teams/${teamId}/quiz_questionnaire`, | ||
| { questionnaire_id: response.data.id }, | ||
| { headers: { Authorization: `Bearer ${token}` } } | ||
| ); | ||
| } catch (linkErr) { | ||
| console.error("Failed to link quiz questionnaire to team:", linkErr); | ||
| } | ||
| navigate(returnTo ? decodeURIComponent(returnTo) : "/questionnaires"); | ||
| return; |
There was a problem hiding this comment.
Do not redirect when team-quiz linking fails.
Line 84 catches link errors, but Line 87 still navigates away. That can leave the new quiz unassigned to the team while showing a success flow.
Suggested fix
- if (mode === "create" && teamId && response.data?.id) {
- try {
- await axiosClient.patch(
- `/teams/${teamId}/quiz_questionnaire`,
- { questionnaire_id: response.data.id },
- { headers: { Authorization: `Bearer ${token}` } }
- );
- } catch (linkErr) {
- console.error("Failed to link quiz questionnaire to team:", linkErr);
- }
- navigate(returnTo ? decodeURIComponent(returnTo) : "/questionnaires");
- return;
- }
+ if (mode === "create" && teamId && response.data?.id) {
+ await axiosClient.patch(
+ `/teams/${teamId}/quiz_questionnaire`,
+ { questionnaire_id: response.data.id },
+ { headers: { Authorization: `Bearer ${token}` } }
+ );
+ navigate(returnTo ? decodeURIComponent(returnTo) : "/questionnaires");
+ return;
+ }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/pages/Questionnaires/QuestionnaireEditor.tsx` around lines 77 - 88, The
create-flow currently always navigates away even if the team-linking PATCH
(axiosClient.patch in the mode === "create" block) fails; change the flow so
navigation only happens after a successful link: move the navigate(returnTo ?
decodeURIComponent(returnTo) : "/questionnaires") and the subsequent return into
the try block after the axiosClient.patch completes, and in the catch (linkErr)
for the patch do not navigate—log or surface the error and let the function
continue without redirecting so the user stays on the page to fix the issue.
| onClick={() => { | ||
| const questionCount = | ||
| typeof numQuestions === "number" ? numQuestions : 0; | ||
|
|
||
| for (let i = 0; i < questionCount; i++) { | ||
| push({ | ||
| id: undefined, | ||
| txt: "", | ||
| weight: "", | ||
| question_type: questionType, | ||
| break_before: 1, | ||
| alternatives: "", | ||
| min_label: "", | ||
| max_label: "", | ||
| seq: values.items.length + 1, | ||
| correct_answer: "", | ||
| }); | ||
| } | ||
| setNumQuestions(""); | ||
| setQuestionType(""); | ||
| }} |
There was a problem hiding this comment.
Guard the Add action when no item type is selected.
Line 525 can persist an empty question_type if the user enters a count but leaves the type dropdown unset.
Suggested fix
onClick={() => {
const questionCount =
typeof numQuestions === "number" ? numQuestions : 0;
+ if (!questionType || questionCount <= 0) return;
for (let i = 0; i < questionCount; i++) {
push({Also applies to: 554-560
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/pages/Questionnaires/QuestionnaireItemsFieldArray.tsx` around lines 516 -
536, The Add action allows pushing items with an empty question_type when
numQuestions is provided but questionType is unset; update the onClick handlers
(the one with push, questionType, numQuestions, setNumQuestions, setQuestionType
and the similar block at the later location) to guard early: check that
questionType is a non-empty string (and numQuestions is a positive number)
before calling push in the loop, and if invalid simply return (or disable the
button upstream); ensure you do the same guard in both occurrences so no item is
created with question_type === "".
| * passed as the request body. | ||
| */ | ||
| export const transformQuestionnaireRequest = (values: QuestionnaireFormValues) => { | ||
| console.log("Original Form Values:", values); |
There was a problem hiding this comment.
Remove console logging of questionnaire payloads.
Line 121 and Line 138 log full form/request objects, including quiz answer data. This should not be emitted in normal runtime paths.
Also applies to: 138-138
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/pages/Questionnaires/QuestionnaireUtils.tsx` at line 121, Remove the two
console.log calls that print full questionnaire payloads (the calls printing
"Original Form Values:" and the other at line 138) so sensitive quiz/answer data
is not emitted in normal runtime; instead either delete them entirely or replace
with a non-production-guarded debug logger (e.g., wrap in if
(process.env.NODE_ENV === 'development') or use a secure logger.debug after
redacting answers) and update the code paths in QuestionnaireUtils.tsx where
these console.log calls occur (search for the exact string "Original Form
Values:" to locate one of them).
| const backendRows = buildRows(maps); | ||
| setAssignedReviews((prev) => { | ||
| // Backend rows take priority; keep any localStorage-only rows | ||
| const byMapId = new Map<number, AssignedReviewRow>(); | ||
| prev.forEach((r) => byMapId.set(r.mapId, r)); | ||
| backendRows.forEach((r) => byMapId.set(r.mapId, r)); | ||
| return Array.from(byMapId.values()); |
There was a problem hiding this comment.
Backend rows can misclassify teammate reviews as normal reviews.
Line 272 builds backend rows without currentUserTeamId, so isTeammateReview falls back to false. Then Line 277 overwrites any prior local row classification by mapId.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/pages/StudentTasks/AssignedReviews.tsx` around lines 272 - 278, buildRows
is called without currentUserTeamId so backendRows'
AssignedReviewRow.isTeammateReview is incorrectly false and then overwrites
local rows during setAssignedReviews merge by mapId; fix by ensuring buildRows
receives currentUserTeamId (e.g., change call to buildRows(maps,
currentUserTeamId) and propagate that param into buildRows) or, when merging in
setAssignedReviews, preserve the existing prev row's isTeammateReview for the
same mapId (use prev's row fields where present, especially isTeammateReview) so
teammate classification isn't lost.
Implements the student quiz workflow in the Expertiza React/TypeScript frontend. Students on assignments that require a quiz must now complete the quiz before they can start their peer review. Instructors and submitting teams can author quiz questions with correct answers directly in the questionnaire editor.
Summary by CodeRabbit
New Features
Bug Fixes
Improvements